-
-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved Oscillators -- fixed #5826
Conversation
Prototype of using multiband wavetables in the Oscillator class. This implementation hijacks the original getsample functions, as such replaces the original waveforms of the square, triangle and saw waves. This is not intended to be merged in it’s current format. The additional code is manly a port from a separate GPL project I have developed and requires refactoring to LMMS coding conventions. https://github.com/curlymorphic/involve.git
Wavetables are held in in array of tables accessed s_waveTables[WaveShape][band][frame]
sine triange saw square implemented
refactored the fft code
The bands parameter is removed from generateSineWaveTable() as sine wave contain no harmonics. define const MIDI_NOTES_PER_TABLE=4. a wave table is generated for every fourth midi note change the defination of static sample_t s_waveTables[WaveShapes::NumWaveShapes][128 / MIDI_NOTES_PER_TABLE][WAVETABLE_LENGTH]; generation of wavetables done via passing a pointer, removing the untidy alloc for each wavetable, that were never deleted
…old for spectral content in user waveform
🤖 Hey, I'm @LmmsBot from github.com/lmms/bot and I made downloads for this pull request, click me to make them magically appear! 🎩
Windows
Linux
macOS🤖{"platform_name_to_artifacts": {"Windows": [{"artifact": {"title": {"title": "32-bit", "platform_name": "Windows"}, "link": {"link": "https://14206-15778896-gh.circle-artifacts.com/0/lmms-1.3.0-alpha.1.176%2Bg06bc4f416-mingw-win32.exe"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/14206?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}], "Linux": [{"artifact": {"title": {"title": "(AppImage)", "platform_name": "Linux"}, "link": {"link": "https://14209-15778896-gh.circle-artifacts.com/0/lmms-1.3.0-alpha.1.176%2Bg06bc4f4-linux-x86_64.AppImage"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/14209?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}], "macOS": [{"artifact": {"title": {"title": "", "platform_name": "macOS"}, "link": {"link": "https://14207-15778896-gh.circle-artifacts.com/0/lmms-1.3.0-alpha.1.176%2Bg06bc4f416-mac10.14.dmg"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/14207?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}]}, "commit_sha": "4ecebd5be6139734e114b1d03f304badc4dc6ef4"} |
So I was thinking about the various modulation modes, how can they cause aliasing even in band-limited wavetable mode, and if there is any easy fix. Sorry if some of the points and examples are (too) obvious, I'm just trying to make sure I don't miss any details and that I understand things correctly.
At a first glance it seems fine: the modulator just says "when playing your waveform, add this offset to the location you plan to be playing". Since the waveform currently playing is appropriate for given frequency it does not cause aliasing when playing at the nominal rate. However, this never happens in practice, since the offset itself likely changes relatively fast, the modulator is not static. For example, if the modulator contains an increasing slope, the modulated oscillator essentially skips through the waveform at a faster rate. This results in a tone with a higher frequency -- and also in aliasing, because the selected wavetable is not appropriate for the increased frequency. Another way to think about it is that the phase shift causes the playback position to skip ahead -- skipping some samples means lowering sampling rate of the one-cycle waveform, resulting in aliasing. I don't see any easy and reliable fix. But perhaps it would be possible to measure the slope in the signal of the modulator, estimate its effect on frequency of the modulated signal, and select appropriate band-limited table for the effective frequency. But it's hard to say how reliable and robust that would be in practice. I.e., what happens when the modulator is just white noise?
Deceptively simple, but possibly even harder to fix. Consider that an oscillator generates a slow triangle at 100 Hz, and there is a modulator that generates a saw wave at 10 kHz. From the point of view of the modulator, the triangle wave is essentially a DC signal, and the AM modulation simply imprints the 10 kHz saw wave into the slow moving triangle wave, adding a lot of high frequency content that will be "misinterpreted" by the sparse 100 Hz sampling. One possible solution I can think of would be to actually keep using WT mode for AM modulators. In one of the last commits I disabled WT mode on oscillators that are used as PM/AM/FM modulators, so that the control signal is not "disturbed" by the ringing. I.e. when you do frequency modulation using saw wave, you expect the frequency to gradually rise and then switch to a different frequency immediately. You don't want the frequency to jump around near the transition because of ringing caused by the limited bandwidth. BUT, perhaps WT mode on AM modulators could be used to prevent aliasing: if the modulator signal itself uses a wavetable with frequency content limited to the same number of bands that the modulated signal uses, whatever AM "imprints" into the waveform of the modulated signal will have limited frequency content. I assume it would not be perfect and the interaction of both signals could still create some high frequency content, but by limiting the frequency content brought by the modulator the result could at least be "less bad".
Looking at the implementation, it is very similar to PM to a large extent. Instead of saying what offset to add to the playback position, the modulator just says by how much to increase the phase / offset for each sample. So there is no guesswork: it should be relatively easy to use the modulator value to figure out the new effective frequency, and pick a suitable wavetable that won't cause aliasing (as suggested by Johanness earlier). Frankly I expected frequency modulation to be the biggest problem, but now it seems to me that AM is the one with the most potential for unpredictable behavior. While it is tempting to try figuring it all out, add the fixes to this PR and make (almost) all aliasing problems in TripleOscillator a thing of the past, I'm worried that it turns out more complex than it seems and it will further delay this PR (and therefore block the refactor). I did not even consider various combinations yet: imagine for example WT OSC1 phase modulated by standard OSC2, which is itself AM modulated by WT OSC3; OSC2 modulator signal therefore "inherits" ringing from OSC3, potentially causing problems when modulating OSC1. Feels like this could get very crazy very quickly (or maybe I'm just too deep in the tunnel and overthinking it). So I would say it is probably better to just wrap this PR up, cross it off the list and continue with further improvements in a new PR. |
So for FM, I guess we both agree. For AM, you brought a good example, and I also could imagine that using WT in the modulator just does what we want. We could try it out with your example. For PM, as you say, it's also modulating the frequency by skipping through the table at a faster rate (the picture below shows a sine modulating a sine). I assume we could pick the right table, since the rate of skipping through the table effectively gives you the frequency. Delaying to another PR? I'd agree: I imagine that FM/PM could be fixed easily by us. Though I also think they are buggily implemented already (as e.g. the volume knob in that song you showed me), so adding a further bug does not harm. The AM example is (probably) fixable by the user picking "WT" for the modulator (but we could test that now with your example, testing of this PR has not been done anyways). |
While the difference is very clear in the spectrogram, before now I did never hear any difference. Now I found a way: Use a single saw wave in 3osc, and add a GLAME Butterworth Lowpass filter at ~80 Hz. Without WT, you'll hear something like noise in the bass frequencies. Without LP, however, the correct harmonics of the saw are often so loud that you almost don't hear that aliasing noise. About the question whether we should enable AA by default... Should we just delay the decision? Maybe we get more feedback from users after merging. |
Try some higher tones; when you play notes around C4 or C5, the harmonics that are reflecting down are pretty weak and hard to notice. But when you play higher notes, it can get pretty nasty on its own, no filters needed: I think enabling it by default would be a good idea. It does not only avoid the issues that are clearly audible, it should also help with "creeping issues" that appear as more instruments are added: all the aliasing noise from each instance gradually adds up, and in the end the signal to noise ratio will be an absolute potato. In mixing terms, it would be probably called "muddy"? Though that's probably a term used when it happens in low-ish frequencies. But it should be undesirable at all ranges.. Musicians coming from technical background may understand what is happening, but people who open LMMS for the first time and start playing with the default synthesizer may not even realize something is wrong. Or perhaps they do, in which case they may think "When I did the same in FL demo yesterday, it sounded nicer. I guess I shouldn't expect free SW to be as good.." and go pirate FL instead. So while this will not fix everything (e.g., open new project, open TripleOsc, switch both mixing modes to FM, try holding random keys and be amazed) and has some downsides (a bit lower performance), I think we should strive for the best first impression and default settings, and this seems like something that should help with that. (Along with having envelopes turned on by default: the clicking is another embarrassing "default technical glitch". But I think that was already discussed before, somewhere.) |
Also, for the GUI appearance: maybe the setting could be inverted? I.e. "go back to legacy oscillators"? So that the orange WT button does not glow all the time as if it was something extra and worth the user's attention (when it is in fact "just the ordinary default"). Or perhaps make it black text (enabled) and gray text (disabled), to make it less "in your face"? That could be reworked along with the change to AA or HQ (still not sure which one of these two would be better, considering that we have a plan to fix PM/FM to make it "really HQ"). |
Wow, OK, for such high notes, it partially even sounds pitched without WT (like with Microtuner). I agree using it as default, however, how do we want to treat old savefiles? (Remember how it changed a sound in one of the songs we tried?) Per the UI, I have really no preferences. I don't know a good abbreviation for "not hq". Well, "LF" for "Low-Fi" is too unusual. Maybe "LO". "SD" for "Standard Definition" sounds wrong, too (aliasing can't be called "standard"). Maybe we'll have more ideas in the next days. |
I would say the original settings should be definitely preserved in old projects. Even if it may be an improvement in most cases, it would still be a silent change to people's projects that were made and tweaked and mixed with the aliasing present, possibly breaking them in subtle ways. So if the tripleoscillator element does not contain any value for Presets should probably also "stay bad", since we cannot say what was the original intention (maybe some of them should sound nasty?). Although I'm willing to be convinced otherwise.. :)) Someone more experienced with music production could perhaps later make a PR that enables WT on suitable presets. |
I think you've just won a ticket for coding that WT is activated by default for new 3oscs, but not for old 3oscs or old projects. Or you delay it to another PR, as you prefer. |
That's why we have DataFile upgrade methods :) |
I was hoping that an upgrade method won't be needed, but from what I tried yesterday, it seems I understood the project loading incorrectly. I expected that a parameter not found in the XML file will get the default value for given type of model ( |
The new design looks better to me, it makes clearer that "HQ" is the normal case. Hopefully, users won't think: "Wow, there's a new feature to make TripleOscillator sound more oldschool" 😃 |
I think it's time for testing review. An incomplete list of things to test:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just checked all your code changes after last review.
// If this oscillator is used to PM or PF modulate another oscillator, take a note. | ||
// The sampling functions will check this variable and avoid using band-limited | ||
// wavetables, since they contain ringing that would lead to unexpected results. | ||
m_isModulator = modulator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if it is necessary to make this a class variable. Currently, for 3osc, it is enough, because in 3osc, an oscillator can only be either modulating or be audio. However, e.g. in zyn it can be both.
You could add the parameter directly to getSample
, I guess?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made it a member variable because it was much simpler and a smaller change. To pass it as an argument to getSample
, I would first need to propagate it from update()
through each updateXX()
and all the calls to different shape specializations. That's like 11 lines to change only for PM and there is 6 updateXX()
variants in total, so 66 changes just to pass the parameter. Compared to one line.
Thinking back, I assume that may be also the reason why m_freq
exists; if everything that defines a "global state" of the oscillator at the time of update was passed as an argument, the argument list would never end. Although I'm not entirely opposed to passing the bool modulator
as a parameter if you think it is better or easier to follow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Good point with the m_freq
comparison, that might be the reason. In zyn, we just pass m_freq
through all the sub functions, but the number of functions is much higher here due to template magic.
What about adding a comment above the m_isModulator
delcaration, which states that it's only a class variable in order to have less function parameters, and that it can be easily converted into a function parameter if needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea with the comment; added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing review finished, no complaints.
@he29-net Great! We have code and test review finished. So it's OK from my side, and others have had enough time to complain 😃 I think we can squash+merge this. Do you mind writing a commit message for the squashed commit? (Or you can do it yourself, not sure if you have those permissions) |
@JohannesLorenz All right, thanks for the review! The PR turned out to be a bit more complex than fixing one or two memory issues. :) As for the commit message, perhaps something like: |
Add a band-limited, alias-free wavetable oscillator option to the `Oscillator` class. Use it by default for Triple Oscillator. Savefiles which do not have this feature enabled (e.g. old savefiles) will be loaded without this feature to keep the sound consistent. Original author: @curlymorphic. Fixed: @he29-net.
This PR finishes earlier work by @curlymorphic, see discussion in #4397.